﻿using System.Text;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using DGen;
using System;
using NLog;

namespace Tab2Sql
{
    

    /// <summary>
    /// Class to adjust the users input to something parseable by the Tab2SQL process. 
    /// The main purpose of this thread is to standardise delimiters so everything is delimited
    /// by the GlobalDelimiter value of four hashes. 
    /// </summary>
    public class PrepareText
    {
        /// <summary>
        ///
        /// </summary>
        const int NUMBER_OF_LINES_TO_SCAN = 5000000;

      private static Logger logger = LogManager.GetCurrentClassLogger ();



        /// <summary>
        /// How many "columns" does input have?
        /// </summary>
        public int EstimatedColumns
        {
            get { return estCols(ref preparedText); }
        }
        private int estCols(ref StringBuilder preparedText)
        {
            string[] Lines = preparedText.ToString().Split('\n');
            int[] values = getOccurances(GlobalDelimiter, Lines);
            int tmpA = values.Max();
            int tmpB = values.Min();
            double tmpC = values.Average();
            int tmpD = values.Distinct().Count();
            return tmpA;
        }
        private int[] getOccurances(string needle, string[] Lines)
        {
            rowCount = Lines.Count();
            int[] values = new int[rowCount];
            string line = "";
            rowCount--;
            for (int i = 0; i < Math.Min(rowCount,numberOfRowsToCheck); i++)
            {
                    line= Lines[i];
                    int y = DText.CountOccurences(needle, line);
                values[i] = y;
            }
            return values;
        }
        //HACK : Sort of. Temporary (11/10/2009 11:17:37) fix to see if this fixes : http://code.google.com/theTableName/tab2sql/issues/detail?id=9
        /// <summary>
        ///
        /// </summary>
        public static readonly string GlobalDelimiter = System.Guid.NewGuid().ToString();
        /// <summary>
        /// output.
        /// </summary>
        private  StringBuilder preparedText;
        /// <summary>
        /// Constructor - all results are added to PreparedText stringbuilder object. Delimiter is updated with best guess as we proceed.
        /// </summary>
        /// <param name="input">Body of text</param>
        /// <param name="userdelimiter">Users preferred delimiter as it currently stands.</param>
        /// <param name="rowLimit">Maximum number of rows to check.</param>
        public PrepareText (string input, string userdelimiter, int rowLimit)
        {
            logger.Trace ("PrepareText Constructor called. User delimiter:{0}.", userdelimiter);



            rowCount = 0;

            numberOfRowsToCheck = rowLimit;
           
            if (numberOfRowsToCheck == -1) { numberOfRowsToCheck = NUMBER_OF_LINES_TO_SCAN; }
            preparedText = new StringBuilder(input.Length*2);
            if (userdelimiter == "FIX")
            {
                userdelimiter = @"\s{3,}";
            }
            Delimiter = userdelimiter;
            string[] Lines = input.Split('\n');
            rowCount = Lines.Count();
            Delimiter = getDelimiter(Lines, Delimiter);
            System.Collections.Hashtable myHash = new System.Collections.Hashtable();
            int lCount = 0;
            foreach (string Line in Lines)
            {
                if (Line.Trim().Length > 0)
                {
                    string tmpLine = Line;
                    string tmp2Line = Line;
                    int posX = 0;
                    int posY = 0;
                    int posZ = 0;
                    while(tmp2Line.Contains(Delimiter))
                    {
                        posX = posZ;
                        posY =tmpLine.IndexOf(Delimiter, posX);
                        if (!myHash.ContainsKey(lCount))
                        {
                            myHash.Add(lCount, posY);
                        }
                        posZ = posY;
                        if (posY == 0) { break; }
                        tmp2Line=tmp2Line.Substring(System.Math.Min(posZ,tmp2Line.Length));
                    }
                    if (Delimiter == ",")
                    {
                        tmpLine = RemoveRogueCommas(tmpLine);
                    }
                    tmpLine = Regex.Replace(tmpLine, Delimiter, GlobalDelimiter);
                    PreparedText.AppendLine(tmpLine);
                    lCount++;

                }
            }
        }

        private int numberOfRowsToCheck;

        private int rowCount;
        /// <summary>
        /// output.
        /// </summary>
        public StringBuilder PreparedText
        {
            get { return preparedText; }
        }
        /// <summary>
        /// Delimiter property.
        /// </summary>
        public string Delimiter { get; set; }

        /// <summary>
        /// This attempts to remove commas which aren't field terminators. 
        /// NOTE : Use with caution. This will change the meaning of sentences, etc. "Eats, shooots, and leaves" becomes "Eats shoots and leaves".
        /// </summary>
        /// <param name="line"></param>
        /// <returns></returns>
        public static string RemoveRogueCommas(string line)
        {


            string lino = line;

            if (line.Contains(@""","""))
            {
                string tmpLine = line;
                tmpLine = tmpLine.Replace(@""",""", "\"87712261XXX787712261XXX\"");
                tmpLine = Regex.Replace(tmpLine, "[0-9],[0-9]", "");
                tmpLine = tmpLine.Replace(",", " ");
                tmpLine = tmpLine.Replace("\"87712261XXX787712261XXX\"", @""",""");
                return tmpLine;
            }
            else if (line.Contains(@"','"))
            {
                string tmpLine2 = line;
                tmpLine2 = tmpLine2.Replace(@"','", "'87712261XXX787712261XXX'");
                tmpLine2 = Regex.Replace(tmpLine2, "[0-9],[0-9]", "");
                tmpLine2 = tmpLine2.Replace(",", " ");
                tmpLine2 = tmpLine2.Replace("'87712261XXX787712261XXX'", @"','");
                return tmpLine2;
            }
            else if (line.Contains(@",""")==true || line.Contains(@""",")==true)
            {
                string tmpLine = line;
                tmpLine = tmpLine.Replace(@",""", "87712261XXX787712261XXX\"");
                tmpLine = tmpLine.Replace(@""",", "\"87712261XXX787712261XXX");
                
                
                tmpLine = tmpLine.Replace(",", " ");
                tmpLine = tmpLine.Replace("87712261XXX787712261XXX\"", @",""");
                tmpLine = tmpLine.Replace("\"87712261XXX787712261XXX", @""",");
                return tmpLine;
            }
            else
            {
                return line;
            }
        }
        private string getDelimiter (string[] line, string delimiter)
        {
            // We're happy with the delimiter if it occurs in the header. If it doesn't, then the below applies.
            // (We are trying to guess the delimiter if it applies)
            //TODO : A setting to stop this applying for when you're only after one columnName.
            //TODO : Ability to check more than just first line if needed.
            //TODO : Ability to suppress this when Auto hasn't been picked. 
            if (line[0].Contains (delimiter) || line[0].Contains (delimiter.Replace (@"\\t", @"\t")))
            {
                //diags.AppendLine("Line 0 Winrar!");
                return delimiter;
            }
            var possibleDelimiters = theDelimiters ();
            Dictionary<int, int> mySum = new Dictionary<int, int> ();
            Dictionary<int, int> myMax = new Dictionary<int, int> ();
            var myDelimiters = new Dictionary<int, string> ();
            int i = 0;

            foreach (string needle in possibleDelimiters)
            {
                string needleSafe = needle;
                if (needle == "\\|")
                {
                    needleSafe = "|";
                }
                int[] occurencesOfDelimiter = getOccurances (needleSafe, line);
                myDelimiters.Add (i, needle);
                mySum.Add (i, occurencesOfDelimiter.Sum ());
                myMax.Add (i, occurencesOfDelimiter.Max ());
                i++;
            
            }
            string returnDelimiter = myDelimiters[0];
            myDelimiters.TryGetValue (0, out returnDelimiter);
            int value = FindKey (mySum, mySum.Values.Max ());
            if (value > 0)
            {
                myDelimiters.TryGetValue (value, out returnDelimiter);
            }
            return returnDelimiter;
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="lookup">
     
        /// </param>
        /// <param name="value">
        /// A <see cref="System.Int32"/>
        /// </param>
        /// <returns>
        /// A <see cref="System.Int32"/>
        /// </returns>
        public static int FindKey(IDictionary<int, int> lookup, int value)
        {
            foreach (var pair in lookup)
            {
                if (pair.Value == value)
                    return pair.Key;
            }
            return 0;
        }
        // God this is bad. (2010-04-24)
        private static string[] theDelimiters()
        {
            var possibleDelimiters = new string[11];
            possibleDelimiters[0] = "\t";
            possibleDelimiters[1] = @"\|";
            possibleDelimiters[2] = @",";
            possibleDelimiters[3] = @"\/";
            possibleDelimiters[4] = @"\s{9,}";
            possibleDelimiters[5] = @"\s{8}";
            possibleDelimiters[6] = @"\s{7}";
            possibleDelimiters[7] = @"\s{6}";
            possibleDelimiters[8] = @"\s{5}";
            possibleDelimiters[9] = @"\s{4}";
            possibleDelimiters[10] = @"\s{3}";
            return possibleDelimiters;
        }


        #region disposal stuff
        /// <summary>
        ///
        /// </summary>
        /// <param name="disposing">
        /// A <see cref="System.Boolean"/>
        /// </param>
        new protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // dispose managed resources
       
                this.Dispose();
            }
            // free native resources
        }
        /// <summary>
        /// 
        /// </summary>
        new public void Dispose()
        {
            logger.Trace("Disposal / garbage collection called explicitly.");
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

    }
}